package com.eci.context.context.core;

import com.eci.common.asset.constant.StringConstant.Symbol;
import com.eci.common.asset.core.Page;
import com.eci.common.util.CustomUtil;
import org.hibernate.ScrollableResults;
import org.hibernate.Session;
import org.hibernate.query.Query;
import org.hibernate.query.internal.NativeQueryImpl;
import org.hibernate.transform.Transformers;
import org.springframework.data.jpa.repository.support.JpaEntityInformation;
import org.springframework.data.jpa.repository.support.SimpleJpaRepository;
import org.springframework.transaction.annotation.Transactional;

import javax.persistence.EntityManager;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;


@SuppressWarnings({ "rawtypes", "unchecked" })
public class BaseRepositoryImpl<T, ID extends Serializable> extends SimpleJpaRepository<T, ID>
		implements BaseRepository<T, ID> {

	private static final String  ALL_XQL = "select * from (%s) ek";
	private static final String ORDER_BY =" order by  ";
	private static final String DESC ="desc";
	
	private final EntityManager em;

	public BaseRepositoryImpl(JpaEntityInformation<T, ID> entityInformation, 
			EntityManager entityManager) {
		
		super(entityInformation, entityManager);
		this.em = entityManager;
	}
	 

	@Override
	public <R> List<R> queryListBySql(String sql, Class<R> clazz) {

		return queryListBySql(sql, clazz, Collections.emptyMap());
	}

	@Override
	public <R> List<R> queryListBySql(String sql, Class<R> clazz, Map<String, Object> paraMap) {
		
		Query sqlQuery = em.unwrap(Session.class).createSQLQuery(sql).unwrap(NativeQueryImpl.class)
				.setResultTransformer(new UnderscoreAliasTransformer(clazz));
		
		return this.setParameter(sqlQuery, paraMap).getResultList();
	}

	@Override
	public <R> List<R> queryListByHql(String hql, Class<R> clazz) {

		return queryListByHql(hql, clazz, Collections.emptyMap());
	}

	@Override
	public <R> List<R> queryListByHql(String hql, Class<R> clazz, Map<String, Object> paraMap) {

		Query hqlQuery = em.unwrap(Session.class).createQuery(hql);
		
		return this.setParameter(hqlQuery, paraMap).list();
	}

	@Override
	public List<Map<String, Object>> queryMapListBySql(String sql) {

		return queryMapListBySql(sql, Collections.emptyMap());
	}

	@Override
	public List<Map<String, Object>> queryMapListBySql(String sql, Map<String, Object> paraMap) {

		Query sqlQuery = em.unwrap(Session.class).createSQLQuery(sql).unwrap(
				NativeQueryImpl.class).setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP);

		return this.setParameter(sqlQuery, paraMap).list();
	}


	@Override
	public Page queryPageByHql(String hqlString, Page page) {

		return queryPageByHql(hqlString, page, Collections.emptyMap());
	}

	@Override
	public Page queryPageByHql(String hqlString, Page page, Map<String, Object> paraMap) {

		int currentPage = page.getPageNum() > 1 ? page.getPageNum() : 1;
		int limit = page.getPageSize()>2000 ? 2000 :page.getPageSize() ;
		if(null !=page.getSortField() && page.getSortField().trim().length()>0 ) {
			hqlString = appendOrderby(hqlString, page, true);
		}
		
		Query query = em.unwrap(Session.class).createQuery(hqlString);

		this.setParameter(query, paraMap);
		
		ScrollableResults sroll = query.scroll();
		sroll.last();
		int total= sroll.getRowNumber() + 1;
		sroll.first();
		
		query.setFirstResult((currentPage - 1) * limit);
		query.setMaxResults(limit); 
		
		return Page.newPage(total,query.list());
	}


	@Override
	public Page queryPageBySql(String sqlString, Page page) {

		return queryPageBySql(sqlString, page, Collections.emptyMap());
	}


	@Override
	public Page queryPageBySql(String sqlString, Page page, Map<String, Object> paraMap) {
		 
		if(null !=page.getSortField() &&  page.getSortField().trim().length()>0 ) {
			
			sqlString = appendOrderby(sqlString, page, false);
		}

		Query sqlQuery = em.unwrap(Session.class).createSQLQuery(sqlString).unwrap(NativeQueryImpl.class)
				.setResultTransformer(new UnderscoreAliasTransformer(page.getClass()));
		
		int total=countBySql(sqlString, paraMap);
		int currentPage = page.getPageNum() > 1 ? page.getPageNum() : 1; 
		int limit = page.getPageSize()>2000 ? 2000 :page.getPageSize() ;
		
		this.setParameter(sqlQuery, paraMap);
		
		sqlQuery.setFirstResult((currentPage - 1) * limit);
		sqlQuery.setMaxResults(limit);
		
		return Page.newPage(total,sqlQuery.getResultList());
	}
	
	

	@Override
	public int countByHql(String hql) {

		return countByHql(hql, Collections.emptyMap());
	}

	@Override
	public int countByHql(String hql, Map<String, Object> paraMap) {

		Query query = em.unwrap(Session.class).createQuery(hql);
		
		ScrollableResults sroll = this.setParameter(query, paraMap).scroll();
		sroll.last();

		return sroll.getRowNumber() + 1;
	}

	@Override
	public int countBySql(String sql) {

		return countBySql(sql, Collections.emptyMap());
	}

	@Override
	public int countBySql(String sql, Map<String, Object> paraMap) {

		Query query = em.unwrap(Session.class).createSQLQuery(sql);
		
		ScrollableResults sroll = this.setParameter(query, paraMap).scroll();
		sroll.last();

		return sroll.getRowNumber() + 1;
	}

	@Override
	@Transactional
	public int executeHql(String hql) {

		return executeHql(hql,Collections.emptyMap());

	}

	@Override
	@Transactional
	public int executeHql(String hql, Map<String, Object> paraMap) {

		Query query = em.unwrap(Session.class).createQuery(hql);
		
		return this.setParameter(query, paraMap).executeUpdate();

	}

	@Override
	@Transactional
	public int executeSql(String sql) {

		return em.unwrap(Session.class).createSQLQuery(sql).executeUpdate();

	}

	@Override
	@Transactional
	public int executeSql(String sql, Map<String, Object> paraMap) {

		Query query = em.unwrap(Session.class).createSQLQuery(sql);
		
		return this.setParameter(query, paraMap).executeUpdate();

	}
	
	
	
	private String appendOrderby(String xql, Page page, final boolean  isHql) {
		
		List<String> sortFields = Stream.of(page.getSortField().split(Symbol.COMMA)).map(String::trim).collect(Collectors.toList());
		List<String> allFields = Stream.of(page.getClass().getDeclaredFields()).map(Field::getName).collect(Collectors.toList());

		Stream<String> stream = null; 
		StringBuffer sb = new StringBuffer();
		if (isHql) {
			sb.append(xql);
			stream = sortFields.stream().filter(allFields::contains);
		} 
		else {
			sb.append(String.format(ALL_XQL, xql));
			stream = sortFields.stream().filter(allFields::contains).map(CustomUtil::humpToLine);
		} 
		List<String> fields = stream.collect(Collectors.toList());
		if(fields.isEmpty()) {
			return xql;  
		}
		
		sb.append(ORDER_BY).append(String.join(Symbol.COMMA, fields)).append(Symbol.SPACE);
		if (page.getSortWay() != null && page.getSortWay().trim().equalsIgnoreCase(DESC)) {
			sb.append(DESC);
		}
		return sb.toString(); 
		
	}
	
	
	private Query setParameter(Query query, Map<String, Object> paraMap) {

		paraMap.entrySet().forEach(entry -> {
			
			String entryKey = entry.getKey();
			Object entryValue = entry.getValue();
			
			if (entryValue instanceof Collection<?>) {
				query.setParameterList(entryKey, (Collection<?>) entryValue);
			} else if (entryValue instanceof Object[]) {
				query.setParameterList(entryKey, (Object[]) entryValue);
			} else {
				query.setParameter(entryKey, entryValue);
			}
			
		});

		return query;
	}



}
